package edu.hawaii.jmotif.sequitur.logic;

/*
 This class is part of a Java port of Craig Nevill-Manning's Sequitur algorithm.
 Copyright (C) 1997 Eibe Frank

 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 2
 of the License, or (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

import java.util.ArrayList;
import java.util.HashMap;
import java.util.StringTokenizer;
import java.util.Vector;

import edu.hawaii.jmotif.timeseries.Timeseries;

/**
 * The Rule.
 * 
 * Adaption of the Java port of Eibe Frank for SAX requirements, documentation
 * {@link sequitur.info}
 * 
 * @author Manfred Lerner
 * 
 */
public class SAXRule {

	// The rule utility constraint demands that a rule be deleted if it is
	// referred to only once. Each
	// rule has an associated reference count, which is incremented when a
	// non-terminal symbol that
	// references the rule is created, and decremented when the non-terminal
	// symbol is deleted. When
	// the reference count falls to one, the rule is deleted.
	
	 /** Current chart data instance. */
	  private MotifChartData chartData;
	

	/** This is static - the total number of rules. */
	protected static int numRules = 0;

	/** Guard symbol to mark beginning and end of rule. */
	protected SAXGuard theGuard;

	/** Counter keeps track of how many times the rule is used in the grammar. */
	protected int count;

	/** The rule's number. Used for identification of non-terminals. */
	protected int number;

	/** Index used for printing. */
	protected int index;

	protected ArrayList<String> arrRules = new ArrayList<String>();

	protected ArrayList<SAXRuleRecord> arrSAXRuleRecords = new ArrayList<SAXRuleRecord>();

	/**
	 * Constructor.
	 */
	public SAXRule() {
		// assign a next number to this rule and increment the global counter
		this.number = numRules;
		numRules++;
		// create a Guard handler for the rule
		this.theGuard = new SAXGuard(this);
		// init other vars
		this.count = 0;
		this.index = 0;
		chartData = new MotifChartData(new Timeseries(new double[0]), 3, 4, 3);
	}

	public SAXRule(MotifChartData chartDataParam) {
		// assign a next number to this rule and increment the global counter
				this.number = numRules;
				numRules++;
				// create a Guard handler for the rule
				this.theGuard = new SAXGuard(this);
				// init other vars
				this.count = 0;
				this.index = 0;
		chartData = chartDataParam;
		
	}

	/**
	 * Report the first symbol of the rule.
	 * 
	 * @return the first rule's symbol.
	 */
	public SAXSymbol first() {
		return this.theGuard.n;
	}

	/**
	 * Report the second symbol of the rule.
	 * 
	 * @return the second rule's symbol.
	 */
	public SAXSymbol last() {
		return this.theGuard.p;
	}

	@SuppressWarnings("unused")
	private ArrayList<String> getRuleList() {
		return arrRules;
	}

	/**
	 * 
	 * @return a list with all the expanded rules
	 */
	protected ArrayList<String> getExpandedRuleList() {

		ArrayList<String> arrExpandedRules = new ArrayList<String>();

		for (SAXRuleRecord container : arrSAXRuleRecords) {
			arrExpandedRules.add(container.getExpandedRuleString());
		}

		return arrExpandedRules;
	}

	public String getRules() {

		Vector<SAXRule> rules = new Vector<SAXRule>(numRules);
		SAXRule currentRule;
		SAXRule referedTo;
		SAXSymbol sym;
		int index;
		int processedRules = 0;
		StringBuffer text = new StringBuffer();

		text.append("Usage\tRule\n");
		rules.addElement(this);
		StringBuilder sbCurrentRule = new StringBuilder();
		;
		while (processedRules < rules.size()) {
			currentRule = rules.elementAt(processedRules);
			text.append(" ");
			text.append(currentRule.count);
			text.append("\tR");
			text.append(processedRules);
			text.append(" -> ");
			for (sym = currentRule.first(); (!sym.isGuard()); sym = sym.n) {
				if (sym.isNonTerminal()) {
					referedTo = ((SAXNonTerminal) sym).r;
					if ((rules.size() > referedTo.index)
							&& (rules.elementAt(referedTo.index) == referedTo)) {
						index = referedTo.index;
					} else {
						index = rules.size();
						referedTo.index = index;
						rules.addElement(referedTo);
					}
					text.append('R');
					text.append(index);

					sbCurrentRule.append('R');
					sbCurrentRule.append(index);
				} else {
					if (sym.value.equals(" ")) {
						text.append('_');
						sbCurrentRule.append('_');
					} else {
						if (sym.value.equals("\n")) {
							text.append("\\n");
							sbCurrentRule.append("\\n");
						} else
							text.append(sym.value);
						sbCurrentRule.append(sym.value);
					}
				}
				text.append(' ');
				sbCurrentRule.append(' ');
			}
			text.append('\n');
			arrRules.add(sbCurrentRule.toString());
			sbCurrentRule = new StringBuilder();
			processedRules++;
		}
		return new String(text);
	}

	public ArrayList<SAXRuleRecord> getSAXRules() {

		Vector<SAXRule> rules = new Vector<SAXRule>(numRules);
		SAXRule currentRule;
		SAXRule referedTo;
		SAXSymbol sym;
		int index;
		int processedRules = 0;
		StringBuffer text = new StringBuffer();

		text.append("Usage\tRule\n");
		rules.addElement(this);
		StringBuilder sbCurrentRule = new StringBuilder();
		while (processedRules < rules.size()) {
			currentRule = rules.elementAt(processedRules);
			text.append(" ");
			text.append(currentRule.count);
			text.append("\tR");
			text.append(processedRules);
			text.append(" -> ");
			for (sym = currentRule.first(); (!sym.isGuard()); sym = sym.n) {
				if (sym.isNonTerminal()) {
					referedTo = ((SAXNonTerminal) sym).r;
					if ((rules.size() > referedTo.index)
							&& (rules.elementAt(referedTo.index) == referedTo)) {
						index = referedTo.index;
					} else {
						index = rules.size();
						referedTo.index = index;
						rules.addElement(referedTo);
					}
					text.append('R');
					text.append(index);

					sbCurrentRule.append('R');
					sbCurrentRule.append(index);
				} else {
					if (sym.value.equals(" ")) {
						text.append('_');
						sbCurrentRule.append('_');
					} else {
						if (sym.value.equals("\n")) {
							text.append("\\n");
							sbCurrentRule.append("\\n");
						} else
							text.append(sym.value);
						sbCurrentRule.append(sym.value);
					}
				}
				text.append(' ');
				sbCurrentRule.append(' ');
			}
			text.append('\n');
			// System.out.println(text.toString());
			SAXRuleRecord saxContainer = new SAXRuleRecord();
			saxContainer.setRuleIndex(processedRules);
			saxContainer.setRuleFrequency(currentRule.count);

			// added by Rasaq
			saxContainer.setRuleIntervalsSD(0);
			saxContainer.setRuleAveragePeriod(0);
			saxContainer.setRuleAverageMotifLength(0);

			saxContainer.setRuleName("R" + processedRules);
			saxContainer.setRuleString(sbCurrentRule.toString());
			arrSAXRuleRecords.add(saxContainer);

			sbCurrentRule = new StringBuilder();
			processedRules++;
		}
		expandRules();
		computeRuleOffsets();
		return arrSAXRuleRecords;
	}

	/**
	 * this function iterates over the SAX containers with the Sequitur rules
	 * rules of a time series and connects the single SAX strings of a rule so
	 * that the rule can be identified. Therefore a connect string or an empty
	 * string can be used
	 * 
	 * Example: Sequitur rule: aabb ccaa bbcc concatentionString: "#" rule after
	 * concatenation: aabb#ccaa#bbcc
	 * 
	 * this is done for all rules of a time series
	 * 
	 * @param concatenationString
	 *            the string which connects the single words of a Sequitur rule
	 * @return the SAXified time series with all the rules in it as expanded
	 *         string
	 */
	public String getSequiturDocument(String concatenationString) {
		HashMap<String, String> ruleMap = new HashMap<String, String>();

		for (SAXRuleRecord cont : arrSAXRuleRecords) {
			ruleMap.put(cont.getRuleName(), cont.getExpandedRuleString());
		}

		String rule0 = arrSAXRuleRecords.get(0).getRuleString();
		StringTokenizer st = new StringTokenizer(rule0, " ");

		StringBuilder sbDocument = new StringBuilder();
		while (st.hasMoreTokens()) {
			String token = st.nextToken();

			if (ruleMap.containsKey(token) == false) {
				sbDocument.append(token);
				sbDocument.append(" ");
				continue;
			}

			String rule = ruleMap.get(token);
			rule = rule.replace(" ", concatenationString);

			// delete final concatenation character
			if (rule.endsWith(concatenationString)) {
				rule = rule.substring(0, rule.length() - 1);
			}
			sbDocument.append(rule);
			sbDocument.append(" ");
		}

		return sbDocument.toString();
	}

	/**
	 * expand the rule of a SAX container into a single string
	 */
	public void expandRules() {

		int currentPositionIndex = 0;
		int workIndex = 0;

		String strTmp = null;

		SAXRuleRecord saxContainer;

		for (currentPositionIndex = 0; currentPositionIndex < arrSAXRuleRecords
				.size(); currentPositionIndex++) {
			saxContainer = arrSAXRuleRecords.get(currentPositionIndex);
			strTmp = saxContainer.getRuleString();

			workIndex = 0;
			while (strTmp.contains("R")) {
				strTmp = strTmp.replaceAll("R" + workIndex + " ",
						arrSAXRuleRecords.get(workIndex).getRuleString());
				if (workIndex == arrSAXRuleRecords.size() - 1)
					workIndex = 0;
				++workIndex;
			}
			strTmp = strTmp.replaceAll("_", "");

			saxContainer.setExpandedRuleString(strTmp);
			System.out.println("rasaq - "+strTmp);
			 SAXString sax = new SAXString(chartData.getFreqData(), " ");
			 ArrayList<SAXPosition> arrPos = sax.getRulePositions(chartData.getSAXWindowSize(),
				        chartData.getSAXPaaSize(),chartData.convert2OriginalSAXAlphabet('1', strTmp));
			System.out.println("rasaq - "+arrPos);
			Periodicity testPeriodicityAnalysis = new Periodicity(arrPos);
			saxContainer.setRuleIntervalsSD(testPeriodicityAnalysis.getSDofPeriods());
			saxContainer.setRuleAveragePeriod(testPeriodicityAnalysis.getAvgPeriod());
			saxContainer.setRuleAverageMotifLength(testPeriodicityAnalysis.getAvgMotifLength());
		}

		for (SAXRuleRecord cont : arrSAXRuleRecords) {
			System.out.println("Rule Index: " + cont.getRuleIndex()
					+ " - Rule Name:" + cont.getRuleName());
			System.out.println("Compressed Rule: " + cont.getRuleString());
			System.out
					.println("Expanded Rule: " + cont.getExpandedRuleString());
			System.out.println();
		}
		System.out.println("Sequitur Document: " + getSequiturDocument("~"));
	}

	/**
	 * compute the offsets for a rule to correlate it to the original time
	 * series
	 */
	public void computeRuleOffsets() {
		int currentPositionIndex = 0;
		SAXRuleRecord saxContainer;
		String saxString = arrSAXRuleRecords.get(0).getExpandedRuleString();
		for (currentPositionIndex = 1; currentPositionIndex < arrSAXRuleRecords
				.size(); currentPositionIndex++) {
			saxContainer = arrSAXRuleRecords.get(currentPositionIndex);
			int offset = saxString
					.indexOf(saxContainer.getExpandedRuleString());
			if (offset != -1) {
				saxContainer.getOffsets().add(offset);
				offset += saxContainer.getExpandedRuleString().length();
				while (offset != -1) {
					offset = saxString.indexOf(
							saxContainer.getExpandedRuleString(), offset);
					if (offset != -1) {
						saxContainer.getOffsets().add(offset);
						offset += saxContainer.getExpandedRuleString().length();
					}
				}
			}
		}
	}

	public ArrayList<SAXRuleRecord> getSAXContainerList() {
		return arrSAXRuleRecords;
	}

	public String asDebugLine() {
		return String.valueOf(this.number);
	}
}
